﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;

namespace Ooaavee.Data.Model
{
    /// <summary>
    /// A collection of tables.
    /// </summary>
    [CollectionDataContract]
    public sealed class TableCollection : Collection<Table>, ITableFinder
    {
        private static readonly Dictionary<RelationalModel, Dictionary<string, Table>> CachedSearchResults = new Dictionary<RelationalModel, Dictionary<string, Table>>();

        /// <summary>
        /// Gets all system tables in this collection.
        /// </summary>
        public ReadOnlyTableCollection SystemTables
        {
            get { return FindByTableType(this, Table.TableTypeSystemTable); }
        }

        /// <summary>
        /// Gets all system views in this collection.
        /// </summary>
        public ReadOnlyTableCollection SystemViews
        {
            get { return FindByTableType(this, Table.TableTypeSystemView); }
        }

        /// <summary>
        /// Gets all user tables in this collection.
        /// </summary>
        public ReadOnlyTableCollection UserTables
        {
            get { return FindByTableType(this, Table.TableTypeUserTable); }
        }

        /// <summary>
        /// Gets all user views in this collection.
        /// </summary>
        public ReadOnlyTableCollection UserViews
        {
            get { return FindByTableType(this, Table.TableTypeUserView); }
        }

        /// <summary>
        /// Searches for the table with the specified name.
        /// </summary>
        /// <param name="name">The String containing the name of the table to get.</param>
        /// <returns>A Table object with the specified name, if found; otherwise a null.</returns>
        /// <exception cref="AmbiguousMatchException">More than one table is found with the specified name.</exception>
        public Table this[string name]
        {
            get { return FindByName(this, name); }
        }

        /// <summary>
        /// Searches for the table with the specified name.
        /// </summary>
        /// <param name="name">The String containing the name of the table to get.</param>
        /// <returns>A Table object with the specified name, if found; otherwise a null.</returns>
        /// <exception cref="AmbiguousMatchException">More than one table is found with the specified name.</exception>
        public Table Find(string name)
        {
            return FindByName(this, name);
        }

        /// <summary>
        /// Searches for tables with the specified table type.
        /// </summary>
        /// <param name="tables">Tables.</param>
        /// <param name="tableType">The table type.</param>
        /// <returns>Tables matching the specified table type.</returns>
        internal static ReadOnlyTableCollection FindByTableType(IList<Table> tables, string tableType)
        {
            return new ReadOnlyTableCollection(tables.Where(table => table.TableType == tableType).ToList());
        }

        /// <summary>
        /// Searches for the table with the specified name.
        /// </summary>
        /// <param name="tables">Tables.</param>
        /// <param name="name">The String containing the name of the table to get.</param>
        /// <returns>A Table object with the specified name, if found; otherwise a null.</returns>
        /// <exception cref="AmbiguousMatchException">More than one table is found with the specified name.</exception>
        internal static Table FindByName(IList<Table> tables, string name)
        {
            if (tables == null)
            {
                throw new ArgumentNullException("tables");
            }

            if (name == null)
            {
                throw new ArgumentNullException("name");
            }

            Table found = null;

            if (tables.Count > 0)
            {
                lock (CachedSearchResults)
                {
                    lock (tables)
                    {
                        var model = tables.First().Parent;

                        Dictionary<string, Table> cached;
                        if (CachedSearchResults.TryGetValue(model, out cached))
                        {
                            if (cached.ContainsKey(name))
                            {
                                found = cached[name];
                            }
                        }

                        if (found == null)
                        {
                            foreach (var table in tables)
                            {
                                if (table.MatchByName(name))
                                {
                                    if (found != null)
                                    {
                                        throw new AmbiguousMatchException("More than one table is found with the specified name.");
                                    }
                                    found = table;
                                }
                            }

                            if (found != null)
                            {
                                if (cached == null)
                                {
                                    cached = new Dictionary<string, Table>();
                                    CachedSearchResults.Add(model, cached);
                                }
                                cached.Add(name, found);
                            }
                        }
                    }
                }
            }
            
            return found;
        }

    }
}